package com.platform.common.security.interceptor.auth;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import javax.annotation.Resource;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.platform.common.core.constant.CacheConstants;
import com.platform.common.core.constant.SecurityConstants;
import com.platform.common.core.utils.StringUtils;
import com.platform.common.redis.service.RedisService;
import com.platform.common.secret.service.SecretEncryptService;
import com.platform.common.security.config.AuthConfigProperties;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;

import cn.hutool.core.util.StrUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

/**
 * @author yzg
 * @date 2023/4/24
 * @apiNote 请求参数处理——加解密
 */
@Slf4j
@Component
public class ParamResultFilter implements Filter{

    @Resource
    private AuthConfigProperties authConfigProperties;
    @Resource
    private SecretEncryptService secretEncryptService;
    @Resource
    private RedisService redisService;

    /**
     * 参数是否加密
     */
//    @Value("${emmp.aesencrypt.paramEncryptAble:false}")
    private Boolean paramEncryptAble=true;

    /**
     * 结果是否加密
     */
//    @Value("${emmp.aesencrypt.resultEncryptAble:false}")
    private Boolean resultEncryptAble=true;

    /**
     * 排除加密解密的接口
     */
//    @Value("${emmp.aesencrypt.excludeUrl:}")
    private String[] excludeUrls={
            "/platApp/info/*", // 获取应用详情
            "/platApp/getApp2ApiList", // 获取所有应用和接口操作权限列表
            "/logininfor",
            "/user/info/*",
            "/user/register",
            "/error",
            "/configuration/ui",
            "/configuration/security",

            "/webjars/*"
    };


    public List<String> excludes;

    /**
     * GET 请求方式
     */
    private static final String GET_REQUEST = "GET";

    /**
     * GET 请求方式
     */
    private static final String OPTIONS = "OPTIONS";

    /**
     * DELETE 请求方式
     */
    private static final String DELETE_REQUEST = "DELETE";
    /**
     * post 请求方式
     */
    private static final String POST_REQUEST = "POST";

    @SneakyThrows
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
        if (!authConfigProperties.isCheck()) {
//        if (true) {
            chain.doFilter(request, response);
            return;
        }
        String allApi=redisService.getCacheObject(CacheConstants.API_ALL_KEY);
        if (StringUtils.isEmpty(allApi)) {
            chain.doFilter(request, response);
            return;
        }
        String[] allApis=allApi.split(",");
        List<String> allApiList = Arrays.asList(allApis);

        HttpServletRequest req = (HttpServletRequest)request;
        String method = req.getMethod();
        String requestUri = req.getRequestURI();
        String appKey = req.getHeader(SecurityConstants.APP_KEY);
        if(!allApiList.contains(requestUri)){
            //被拦截校验的 前提————处于安全管理接口管理列表中
            chain.doFilter(request, response);
            return;
        }
        log.debug("当前拦截请求 url = {}", requestUri);

        String innerService = req.getHeader(SecurityConstants.INNER_SERVICE);
        log.debug("当前请求所属{}", innerService);
        if (StringUtils.isNotEmpty(innerService)) {
            //内部请求 ——直接跳过加解密
            chain.doFilter(request, response);
            return;
        }

        if (DELETE_REQUEST.equals(method) || OPTIONS.equals(method)) {
            // 这里是过滤器注册转发
            chain.doFilter(request, response);
            return;
        }

        // 允许跳过的接口
        if (handleExcludeUrl(req)) {
            // 这里是过滤器注册转发
            chain.doFilter(request, response);
            return;
        }
        log.debug("开始进行过滤处理");
        // 是否参数加密
        RequestWrapper requestWrapper = new RequestWrapper((HttpServletRequest)request);
        // 处理智搜的GET加密请求
        if (GET_REQUEST.equals(method) && paramEncryptAble) {
            // get请求暂时全部放过
            if(requestWrapper == null) {
                chain.doFilter(request, response);
            } else {
                chain.doFilter(requestWrapper, response);
            }
            return;
//            this.dealGetRequestDecode(requestWrapper,appKey,requestUri);
        }
        // 如果可以json化一般都是swagger请求就直接请求
        boolean jsonFlag = false;
        // 处理post请求
        if (POST_REQUEST.equals(method)) {
            StringBuilder str = new StringBuilder();
            String line;
            BufferedReader reader;
            reader = requestWrapper.getReader();
            while ((line = reader.readLine()) != null) {
                str.append(line);
            }
            String json;
            if (StrUtil.isNotEmpty(str.toString())) {
                try {
                    JSONObject object = JSON.parseObject(str.toString());
                    json = object.toJSONString();
                    requestWrapper.setBody(json.getBytes(StandardCharsets.UTF_8));
                    // 能json
                    jsonFlag = true;
                } catch (Exception e) {
                    jsonFlag = false;
                    log.warn("不是常规的json字符串_进行解密字符串:{},异常内容：{}", str.toString(), e.getMessage());
                }
            }
            // 如果不是swagger传过来的数据否则就对post进行解密且对返回的数据加密
            if (!jsonFlag) {
                // post请求内容需要解密
                log.info("解密之前的字符串：{}",str.toString());
                json = this.secretEncryptService.onlyDecode(str.toString(), appKey,requestUri);
                log.info("解密之后的字符串:{}", json);
                requestWrapper.setBody(json.getBytes(StandardCharsets.UTF_8));
            }
            // 结果是否加密
            if (resultEncryptAble && !jsonFlag) {
                log.info("返回结果进行加密");
                // 且对返回数据进行加密 ,此地方代码过于冗余后续可能需要对整个方法进行优化
                this.encryptResultData(chain, response, requestWrapper,appKey,requestUri);
                log.info("返回结果进行加密完成");
            } else {
                chain.doFilter(requestWrapper, response);
            }
        }else {
            if(requestWrapper == null) {
                chain.doFilter(request, response);
            } else {
                chain.doFilter(requestWrapper, response);
            }
            return;
        }

    }

    /**
     * @description: 加密返回数据
     * @param: [chain,
     *             response, requestWrapper]
     * @return: void
     * @date: 2022/4/12 18:16
     */
    public void encryptResultData(FilterChain chain, ServletResponse response, RequestWrapper requestWrapper,String appKey,String requestUri) {
        // 且对返回数据进行加密 ,此地方代码过于冗余后续可能需要对整个方法进行优化
        try {
            // 请注意响应处理 包装响应对象 res 并缓存响应数据,只有需要加密数据才使用这个 ResponseWrapper
            ResponseWrapper responseWrapper = new ResponseWrapper((HttpServletResponse)response);
            // 执行业务逻辑 交给下一个过滤器或servlet处理
            // 这里是过滤器注册转发
            chain.doFilter(requestWrapper, responseWrapper);
            // 是否结果加密
            byte[] resData = responseWrapper.getResponseData();
            // 设置响应内容格式，防止解析响应内容时出错
            // responseWrapper.setContentType("text/plain;charset=UTF-8");
            // 加密响应报文并响应
            log.info("加密前返回结果：{}",resData);
            String resultEncrypt = secretEncryptService.onlyEncode(new String(resData), appKey,requestUri);
            log.info("加密后返回结果:{}",resultEncrypt);
            // 这里得用原始的流去处理返回 切记
            PrintWriter out = response.getWriter();
            out.print(resultEncrypt);
            out.flush();
            out.close();

        } catch (Exception e) {
            log.error("返回数据加密失败：{}", e.getMessage());
            try {
                getFailResponse((HttpServletResponse)response);
            } catch (IOException ioException) {
                log.error("io异常：{}", ioException.getMessage());
            }

        }
    }

    /**
     * 前端get请求 加密之后需要解码请求参数
     *
     * @param requestWrapper
     */
    private void dealGetRequestDecode(RequestWrapper requestWrapper,String appKey,String requestUri) {
        Map<String, String[]> parameterMap = requestWrapper.getParameterMap();
        Map<String, Object> paras = new HashMap<>();
        // 这里get 请求加密的value
        parameterMap.forEach((paramKey, encryptValue) -> {
            for (String s : encryptValue) {
                try {
                    // 解密请求的
                    String params = secretEncryptService.onlyEncode(s,appKey,requestUri);
                    JSONObject jsonObject = JSON.parseObject(params);
                    jsonObject.forEach((name, value) -> {
                        String[] vs = {value + ""};
                        paras.put(name, vs);
                    });
                } catch (Exception e) {
                    log.info("get请求解密异常：{}", e.getMessage());
                }
            }

        });
        requestWrapper.addAllParameters(paras);
        log.info("paramsMap:{}", parameterMap);
    }

    private void getFailResponse(HttpServletResponse response) throws IOException {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        PrintWriter out = null;
        out = response.getWriter();
        // out.write("{\n" +
        // " \"status\":"+ Constant.ENCRYPT_FAIL +",\n" +
        // " \"message\": null,\n" +
        // " \"data\": []\n" +
        // "}");
        // 加密后的错误消息
        out.write("服务端加密异常");
        out.flush();
        out.close();
    }

    @Override
    public void destroy() {}

    @Override
    public void init(FilterConfig filterConfig) {
//        List<String> whitesList = authConfigProperties.getWhites();
        List<String> whitesList = new ArrayList<>();
        Collections.addAll(whitesList, excludeUrls);
        // 去除 多个*的情况
        whitesList.stream().map(s -> s=s.replaceAll("\\*+","\\*")).collect(Collectors.toList());
        excludes=whitesList;
    }

    private boolean handleExcludeUrl(HttpServletRequest request) {
        if (CollectionUtils.isEmpty(excludes)) {
            return false;
        }
        String url = request.getServletPath();
        for (String pattern : excludes) {
            pattern=pattern.replaceAll("\\*+","\\*");
            Pattern p = Pattern.compile("^" + pattern);
            Matcher m = p.matcher(url);
            if (m.find()) {
                return true;
            }
        }
        return false;
    }


}
